/*
Copyright 2013-present Barefoot Networks, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#include "tofino/intrinsic_metadata.p4"
#include "tofino/constants.p4"
#include "tofino/stateful_alu_blackbox.p4"

/* Sample P4 program */
header_type ethernet_t {
    fields {
        dstAddr : 48;
        srcAddr : 48;
        etherType : 16;
    }
}

header_type vlan_tag_t {
    fields {
        pri     : 3;
        cfi     : 1;
        vlan_id : 12;
        etherType : 16;
    }
}

header_type ipv4_t {
    fields {
        version : 4;
        ihl : 4;
        diffserv : 8;
        totalLen : 16;
        identification : 16;
        flags : 3;
        fragOffset : 13;
        ttl : 8;
        protocol : 8;
        hdrChecksum : 16;
        srcAddr : 32;
        dstAddr: 32;
    }
}

header_type ipv6_t {
    fields {
        version : 4;
        trafficClass : 8;
        flowLabel : 20;
        payloadLen : 16;
        nextHdr : 8;
        hopLimit : 8;
        srcAddr : 128;
        dstAddr : 128;
    }
}

header_type tcp_t {
    fields {
        srcPort : 16;
        dstPort : 16;
        seqNo : 32;
        ackNo : 32;
        dataOffset : 4;
        res : 3;
        ecn : 3;
        ctrl : 6;
        window : 16;
        checksum : 16;
        urgentPtr : 16;
    }
}

header_type udp_t {
    fields {
        srcPort : 16;
        dstPort : 16;
        hdr_length : 16;
        checksum : 16;
    }
}

parser start {
    return parse_ethernet;
}

header ethernet_t ethernet;

parser parse_ethernet {
    extract(ethernet);
    return select(latest.etherType) {
        0x8100 : parse_vlan_tag;
        0x800 : parse_ipv4;
        0x86dd : parse_ipv6;
        default: ingress;
    }
}

#define IP_PROTOCOLS_TCP 6
#define IP_PROTOCOLS_UDP 17

header ipv4_t ipv4;
header ipv6_t ipv6;

parser parse_ipv4 {
    extract(ipv4);
    return select(latest.fragOffset, latest.protocol) {
        IP_PROTOCOLS_TCP : parse_tcp;
        IP_PROTOCOLS_UDP : parse_udp;
        default: ingress;
    }
}

parser parse_ipv6 {
    extract(ipv6);
    return select(latest.nextHdr) {
        IP_PROTOCOLS_TCP : parse_tcp;
        IP_PROTOCOLS_UDP : parse_udp;
        default : ingress;
    }
}

header vlan_tag_t vlan_tag;

parser parse_vlan_tag {
    extract(vlan_tag);
    return select(latest.etherType) {
        0x800 : parse_ipv4;
        default : ingress;
    }
}

header tcp_t tcp;

parser parse_tcp {
    extract(tcp);
    return ingress;
}

header udp_t udp;

parser parse_udp {
    extract(udp);
    return ingress;
}

header_type routing_metadata_t {
    fields {
        drop: 1;
    }
}

metadata routing_metadata_t /*metadata*/ routing_metadata;

field_list ipv4_field_list {
    ipv4.version;
    ipv4.ihl;
    ipv4.diffserv;
    ipv4.totalLen;
    ipv4.identification;
    ipv4.flags;
    ipv4.fragOffset;
    ipv4.ttl;
    ipv4.protocol;
    ipv4.srcAddr;
    ipv4.dstAddr;
}

field_list_calculation ipv4_chksum_calc {
    input {
        ipv4_field_list;
    }
    algorithm : csum16;
    output_width: 16;
}

calculated_field ipv4.hdrChecksum {
    update ipv4_chksum_calc;
}

@pragma command_line --no-dead-code-elimination

/* Match and action definition for EXM with direct addressed action */

action direct_action_exm_1(ipsrcAddr, tcpSrcport) {
    modify_field(ipv4.srcAddr, ipsrcAddr);
    modify_field(tcp.srcPort, tcpSrcport);
}

action direct_action_exm_2(ipsrcAddr, ipdstAddr) {
    modify_field(ipv4.srcAddr, ipsrcAddr);
    modify_field(ipv4.dstAddr, ipdstAddr);
}

action direct_action_exm_3(srcMac, dstMac) {
    modify_field(ethernet.srcAddr, srcMac);
    modify_field(ethernet.dstAddr, dstMac);
}

table exm_with_direct_action {
    reads {
       ipv4.dstAddr : exact;
    }
   actions {
      direct_action_exm_1;
      direct_action_exm_2;
      direct_action_exm_3;
   }
   size : 16384;
}

action hop(ttl, egress_port) {
    add_to_field(ttl, -1);
    modify_field(ig_intr_md_for_tm.ucast_egress_port, egress_port);
}

action hop_ipv4(egress_port) {
    hop(ipv4.ttl, egress_port);
}

/* Match and action definition for TCAM with direct addressed action */

action direct_action_tcam_1(srcmacAddr) {
    modify_field(ethernet.srcAddr, srcmacAddr);
}

action direct_action_tcam_2(ipsrcAddr) {
    modify_field(ipv4.srcAddr, ipsrcAddr);
}

action direct_action_tcam_3(ipsrcAddr, dstMac) {
    modify_field(ipv4.srcAddr, ipsrcAddr);
    modify_field(ethernet.dstAddr, dstMac);
}

table tcam_with_direct_action {
    reads {
        ipv4.dstAddr : ternary;
    }
    actions {
        direct_action_tcam_1;
        direct_action_tcam_2;
        direct_action_tcam_3;
    }
    size: 8192;
}

table lpm {
    reads {
        ipv4.dstAddr : lpm;
    }
    actions {
        direct_action_tcam_1;
        direct_action_tcam_2;
        direct_action_tcam_3;
    }
    size: 8192;
}

table exm_valid {
    reads {
        ipv4.valid : exact;
        tcp.valid : exact;
    }
    actions {
        hop_ipv4;
    }
}

table tcam_valid {
    reads {
        ipv4.valid : exact;
        tcp.valid : ternary;
    }
    actions {
        hop_ipv4;
    }
}

/* Match and action definition for EXM with indirect addressed action */

action exm_act_1(srcMac, dstMac) {
    modify_field(ethernet.srcAddr, srcMac);
    modify_field(ethernet.dstAddr, dstMac);
}

action exm_act_2(ipsrcAddr, tcpSport) {
    modify_field(ipv4.srcAddr, ipsrcAddr);
    modify_field(tcp.srcPort, tcpSport);
}

action exm_act_3(dstMac, ipdstAddr) {
    modify_field(ethernet.dstAddr, dstMac);
    modify_field(ipv4.dstAddr, ipdstAddr);
}

action_profile act_profile_1 {
    actions {
        exm_act_1;
        exm_act_2;
        exm_act_3;
    }
    size: 2048;
}

table exm_with_indirect_action {
    reads {
        ipv4.dstAddr : exact;
        ipv4.srcAddr : exact;
    }
    action_profile: act_profile_1;
    size : 16384;
}

/* Match and action definition for TCAM with indirect addressed action */

action tcam_act_1(srcMac, ipdstAddr) {
    modify_field(ethernet.srcAddr, srcMac);
    modify_field(ipv4.dstAddr, ipdstAddr);
}

action tcam_act_2(ipsrcAddr, ipdstAddr, tcpSport) {
    modify_field(ipv4.srcAddr, ipsrcAddr);
    modify_field(ipv4.dstAddr, ipdstAddr);
    modify_field(tcp.srcPort, tcpSport);
}

action tcam_act_3(dstMac, ipdstAddr) {
    modify_field(ethernet.dstAddr, dstMac);
    modify_field(ipv4.dstAddr, ipdstAddr);
}

action_profile act_profile_2 {
    actions {
        tcam_act_1;
        tcam_act_2;
        tcam_act_3;
    }
    size : 2048;
}

table tcam_with_indirect_action {
    reads {
        ethernet.srcAddr : ternary;
        ethernet.dstAddr : ternary;
    }
    action_profile : act_profile_2;
    size : 8192;
}

action nop() {
}

action wide_action(dstMac, srcMac, ipsrcAddr, ipdstAddr, srcPort, dstPort) {
    modify_field(ethernet.dstAddr, dstMac);
    modify_field(ethernet.srcAddr, srcMac);
    modify_field(ipv4.dstAddr, ipdstAddr);
    modify_field(ipv4.srcAddr, ipsrcAddr);
    modify_field(tcp.srcPort, srcPort);
    modify_field(tcp.dstPort, dstPort);
}

table exm_with_wide_action_data {
    reads {
        ipv4.dstAddr : exact;
        ipv4.srcAddr : exact;
        vlan_tag.vlan_id : exact;
    }
    actions {
        wide_action;
        nop;
    }
    size: 16384;
}

table tcam_range_1 {
    reads {
        ipv4.dstAddr : ternary;
        tcp.dstPort : range;
    }
    actions {
      nop;
      hop_ipv4;
    }
    size : 512;
}

table tcam_range_2 {
    reads {
        ipv4.dstAddr : ternary;
        tcp.dstPort : range;
        tcp.srcPort : range;
    }
    actions {
      nop;
      hop_ipv4;
    }
    size : 1024;
}

action atcam_action (dstMac, ipSrc) {
    modify_field(ethernet.dstAddr, dstMac);
    modify_field(ipv4.srcAddr, ipSrc);
}

@pragma atcam_partition_index vlan_tag.vlan_id
table atcam_tbl {
    reads {
        tcp.valid : ternary;
        vlan_tag.vlan_id : exact;
        ipv4.dstAddr : ternary;
    }
    actions {
        atcam_action;
    }
    size : 100000;
}

counter cntr {
    type : packets;
    static : exm_with_indirect_action_and_stats;
    instance_count : 2048;
}

register r {
    width  : 32;
    instance_count: 2048;
}
blackbox stateful_alu r_alu1 {
    reg: r;
    initial_register_lo_value: 1;
    update_lo_1_value: register_lo + 1;
}
blackbox stateful_alu r_alu2 {
    reg: r;
    initial_register_lo_value: 1;
    update_lo_1_value: register_lo + 100;
}

action act_1(srcMac, dstMac, statsIdx, stfulIdx) {
    modify_field(ethernet.srcAddr, srcMac);
    modify_field(ethernet.dstAddr, dstMac);
    count(cntr, statsIdx);
    r_alu1.execute_stateful_alu(stfulIdx);
}

action act_2(ipsrcAddr, tcpSport, stfulIdx) {
    modify_field(ipv4.srcAddr, ipsrcAddr);
    modify_field(tcp.srcPort, tcpSport);
    r_alu2.execute_stateful_alu(stfulIdx);
}

action act_3(dstMac, ipdstAddr, statsIdx) {
    modify_field(ethernet.dstAddr, dstMac);
    modify_field(ipv4.dstAddr, ipdstAddr);
    count(cntr, statsIdx);
}

@pragma bind_indirect_res_to_match r
@pragma bind_indirect_res_to_match cntr
action_profile act_profile_3 {
    actions {
        act_1;
        act_2;
        act_3;
    }
    size : 2048;
}

table exm_with_indirect_action_and_stats {
    reads {
        ipv4.dstAddr : exact;
    }
    action_profile : act_profile_3;
    size : 8192;
}

counter cntr1 {
    type : packets;
    static : tcam_with_indirect_action_and_stats;
    instance_count : 2048;
}

register r1 {
    width  : 32;
    instance_count: 2048;
}
blackbox stateful_alu r1_alu1 {
    reg: r1;
    initial_register_lo_value: 1;
    update_lo_1_value: register_lo + 1;
}
blackbox stateful_alu r1_alu2 {
    reg: r1;
    initial_register_lo_value: 1;
    update_lo_1_value: register_lo + 100;
}

action act_11(srcMac, dstMac, statsIdx, stfulIdx) {
    modify_field(ethernet.srcAddr, srcMac);
    modify_field(ethernet.dstAddr, dstMac);
    count(cntr1, statsIdx);
    r1_alu1.execute_stateful_alu(stfulIdx);
}

action act_21(ipsrcAddr, tcpSport, stfulIdx) {
    modify_field(ipv4.srcAddr, ipsrcAddr);
    modify_field(tcp.srcPort, tcpSport);
    r1_alu2.execute_stateful_alu(stfulIdx);
}

action act_31(dstMac, ipdstAddr, statsIdx) {
    modify_field(ethernet.dstAddr, dstMac);
    modify_field(ipv4.dstAddr, ipdstAddr);
    count(cntr1, statsIdx);
}

@pragma bind_indirect_res_to_match r1
@pragma bind_indirect_res_to_match cntr1
action_profile act_profile_4 {
    actions {
        act_11;
        act_21;
        act_31;
    }
    size : 2048;
}

table tcam_with_indirect_action_and_stats {
    reads {
        ethernet.srcAddr : ternary;
        ethernet.dstAddr : ternary;
    }
    action_profile : act_profile_4;
    size : 2048;
}

@pragma meter_pre_color_aware_per_flow_enable 1
meter meter_1 {
    type : bytes;
    static : exm_with_indirect_meter;
    result : ipv4.diffserv;
    pre_color : ipv4.diffserv;
    instance_count : 500;
}

action act_12(srcMac, dstMac, meterIdx) {
    modify_field(ethernet.srcAddr, srcMac);
    modify_field(ethernet.dstAddr, dstMac);
    execute_meter(meter_1, meterIdx, ipv4.diffserv, ipv4.diffserv);
}

action act_22(ipsrcAddr, tcpSport) {
    modify_field(ipv4.srcAddr, ipsrcAddr);
    modify_field(tcp.srcPort, tcpSport);
}

@pragma bind_indirect_res_to_match meter_1
action_profile act_profile_5 {
    actions {
        act_12;
        act_22;
    }
    size : 2048;
}

table exm_with_indirect_meter {
    reads {
        ethernet.dstAddr : exact;
    }
    action_profile : act_profile_5;
    size : 2048;
}

@pragma meter_pre_color_aware_per_flow_enable 1
meter meter_2 {
    type : bytes;
    static : tcam_with_indirect_meter;
    result : ipv4.diffserv;
    pre_color : ipv4.diffserv;
    instance_count : 500;
}

action act_61(ipsrcAddr, dstMac, meterIdx) {
    modify_field(ipv4.srcAddr, ipsrcAddr);
    modify_field(ethernet.dstAddr, dstMac);
    execute_meter(meter_2, meterIdx, ipv4.diffserv, ipv4.diffserv);
}

action act_62(ipsrcAddr, ipdstAddr) {
    modify_field(ipv4.srcAddr, ipsrcAddr);
    modify_field(ipv4.dstAddr, ipdstAddr);
}

@pragma bind_indirect_res_to_match meter_2
action_profile act_profile_6 {
    actions {
        act_61;
        act_62;
    }
    size : 2048;
}

table tcam_with_indirect_meter {
    reads {
        ipv4.dstAddr : ternary;
    }
    action_profile : act_profile_6;
    size : 2048;
}

action mod_field(cond, value) {
    modify_field_conditionally(ethernet.srcAddr, cond, value);
}

table mod_field_conditionally_tbl {
    reads {
        ethernet.dstAddr : exact;
    }
    actions {
        mod_field;
    }
    size : 2048;
}


/* Main control flow */
control ingress {
    /* A general principle : Always keep the exact match tables ahead of the
     * ternary tables in the same stage, except first stage. Logic relating to next-table
     * will cause the Tofino model not to launch a lookup on th exact match
     * tables if the order is reversed.
     */
    /* Exm with direct addressed action */
    apply(exm_with_direct_action);
    /* TCAM with direct addressed action */
    apply(tcam_with_direct_action);
    /* Exm with immediate action data */
    //apply(exm_with_immediate_action_data);
    /* TCAM with immediate action data */
    //apply(tcam_with_immediate_action_data);
    /* Exm with indirect addressed action */
    apply(exm_with_indirect_action);
    /* TCAM with indirect addressed action */
    apply(tcam_with_indirect_action);
    /* Exm with indirect addressed action & stats */
    apply(exm_with_indirect_action_and_stats);
    /* TCAM with indirect addressed action & stats */
    apply(tcam_with_indirect_action_and_stats);
    /* Exm with with wide match spec */
    //apply(exm_wide_match_spec);
    /* TCAM with wide match spec */
    //apply(tcam_wide_match_spec);
    /* Exm with wide action data */
    apply(exm_with_wide_action_data);
    /* Valid bits only */
    apply(exm_valid);
    apply(tcam_valid);
    /* LPM */
    apply(lpm);
    /* TCAM with one range field */
    apply(tcam_range_1);
    /* TCAM with two range fields */
    apply(tcam_range_2);
    /* ATCAM */
    apply(atcam_tbl);
    /* Hash Action */
}

control egress {
    /* EXM with indirect Meter */
    apply(exm_with_indirect_meter);
    apply(tcam_with_indirect_meter);
    apply(mod_field_conditionally_tbl);

}
